<template>
  <div class="list-scroller" ref="listScroller" @scroll="update" :style="{'minHeight': typeof(minHeight)=='number'?minHeight+'px':minHeight}">
    <div
      class="list-scroller-holder" 
      :style="{'minHeight': `${totalHeight}px`}"
    >
      <div 
        class="list-scroller-wrapper" :class="{visible:visible}"
        :style="{ transform: `translateY(${translate}px)` }"
      >
        <div class="list-scroller-item" v-for="(item, index) in showList" :key="item[itemKey] || index">
          <slot
            :item="item"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { computed, onMounted, ref, watch } from 'vue';
import {throttle} from '@/utils'
/* 
海量数据虚拟滚动组件
参数	说明	类型	必填	默认值
list	需要渲染的数据	Array	是	-
itemKey	数组的唯一标识字段，可以很大程度提升渲染性能	String	是	-
minHeight	可视区域最小高度	Number	否	500
使用示例：
<list-scroller
:list="list"
itemKey="key">
  <!-- 组件插槽会返回当前内容item，可自定义滚动内容 -->
  <template v-slot="{item}">
    <div class="scroll-item">
      {{ item.value }}
    </div>
  </template>
</list-scroller>
 */
export default {
  name: 'ListScroller',
  props: {
    list: { // 需要渲染的数据
      type: Array,
      default: () => {
        return [];
      },
      require: true
    },
    itemKey: { // 数组的唯一标识字段，可以很大程度提升性能
      type: String,
      default: 'id',
      require: true
    },
    minHeight: { // 最小可视区域高度
      type: Number | String,
      default: 300
    }
  },
  setup(props, ctx){
    const visible = ref(false) //初始化完成之前隐藏
    const showList = ref(props.list.length?[props.list[0]]:[]) // 当前渲染的数据
    const translate = ref(0) // 当前渲染数据区域的Y轴偏移数，需要通过偏移数使滚动过程中渲染数据一直出现在可视区域内
    const listScroller = ref(null)
    const itemHeight = ref(0) //单个列表的高度
    const viewHeight = ref(0) // 可视区域高度
    const totalHeight = computed(()=>{
      return itemHeight.value * props.list.length;
    })

    onMounted(()=>{
      initScroller()
      window.onresize = initScroller

    })
    function initScroller(){
      viewHeight.value = listScroller.value.offsetHeight
      let item = listScroller.value.querySelector('.list-scroller-item')
      itemHeight.value = item && item.offsetHeight || 0
      update()
      // 当传入的数据发生变化时，需要重新渲染数据
      watch(()=>props.list, update)
      visible.value = true
    }

    //滚动时采用截留函数提高滚动的性能
    const update = throttle(()=>{
       // 更新当前显示的数组数据
      let scrollTop = listScroller.value.scrollTop;
      let viewNum = Math.ceil(viewHeight.value / itemHeight.value); // 可视区域可显示的最多条数
      let showStart = Math.floor(scrollTop / itemHeight.value); // 可视区域显示的第一数据下标
      // 利用VUE中Diff算法的复用机制，我们可以截取可视区域的上一屏第一条至下一屏最后一条
      // 这样滚动时可以快速复用，避免滚动后再渲染DOM出现短暂空白。
      let start = showStart - viewNum > 0 ? showStart - viewNum: 0; // 上一屏第一条下标
      let end = showStart + (2 * viewNum) < props.list.length ? showStart + (2 * viewNum) : props.list.length; // 下一屏最后一条下标
      showList.value = props.list.slice(start, end);
      translate.value = start * itemHeight.value;
    },50)

    return {
      listScroller,
      visible,
      viewHeight,
      totalHeight,
      showList,
      translate,
      update
    }
  }
}
</script>
<style lang="less" scoped>
.list-scroller{
  height: 100%;
  position: relative;
  overflow: auto;
  overscroll-behavior: contain;
}
.list-scroller-holder, .list-scroller-wrapper{
  position: absolute;
  top: 0;
  width: 100%;
}
.list-scroller-wrapper {
  visibility: hidden;
  &.visible {
    visibility: visible;
  }
}
.list-scroller-item{
  display: flex;
  align-items: center;
}
</style>